Skip to content

Conversation

@mikebenfield
Copy link
Contributor

@mikebenfield mikebenfield commented Jul 7, 2025

Specifically,

  1. Introduce a PlaintextType::ExternalStruct, and allow referring to such a type in .aleo code by a Locator.

  2. Allow casting to external struct types.

  3. Let two struct types be considered the same type as long as they are structurally the same and have the same local names. This funky ruleset is effectively here for backwards compatibility. In more detail:

/// Equivalence of structs means they have the same local names (regardless of whether
/// they're local or external), and their members have the same names and equivalent
/// types in the same order, recursively.
///
/// Equivalence of arrays means they have the same length and their element types are
/// equivalent.
///
/// This definition of equivalence was chosen to balance these concerns:
///
/// 1. All programs from before the existence of external structs will continue to work -
/// thus it's necessary for a struct created from another program to be considered equivalent
/// to a local one with the same name and structure, as in practice that was the behavior.
/// 2. We don't want to allow a fork. Thus we do need to check names, not just structural
/// equivalence - otherwise we could get a program deployable to a node which is using
/// this check, but not deployable to a node running an earlier SnarkVM.

Also note this other usecase from a user:

currently we got in an issue where one mainnet deployed contract imports tokenRegistry, and the contract we are writing for A imports that contract and A contract. but, A contract and tokenRegistryContract have same struct names so we cannot compile that.If we cannot change struct name in A contract, we need to wait for snarkVM update, just to compile the contracts

@mikebenfield mikebenfield requested review from d0cd and vicsn July 7, 2025 01:58
@mikebenfield mikebenfield marked this pull request as draft July 7, 2025 15:56
@mikebenfield mikebenfield marked this pull request as ready for review July 7, 2025 15:57
@mikebenfield mikebenfield force-pushed the workspace-dependencies branch from dd47c44 to f6e272f Compare July 7, 2025 22:34
@mikebenfield mikebenfield force-pushed the workspace-dependencies branch from f6e272f to 5ec993c Compare July 8, 2025 02:24
Base automatically changed from workspace-dependencies to staging July 12, 2025 09:55
Specifically,

1. Introduce a PlaintextType::ExternalStruct, and allow referring
to such a type in .aleo code by a Locator.

2. Allow casting to external struct types.

3. Let two struct types be considered the same type as long as
they are structurally the same.
@mikebenfield
Copy link
Contributor Author

Rebased.

Copy link
Collaborator

@vicsn vicsn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a check in check_transaction that deployments using an external struct are only enabled from ConsensusVersion::V10 onwards. You can put in dummy heights for the new consensus version.

And while you're at it, consider refactoring ConsensusVersion-related checks into a separate function called by check_transaction?

@mikebenfield
Copy link
Contributor Author

I moved some of the consensus version checks into a new function, but some others are scattered throughout check_transaction so this may or may not be what you had in mind.

Copy link
Collaborator

@vicsn vicsn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice.

I would appreciate a short walk through synthesizer/program/src/logic/instruction/operation/cast.rs during the next Leo Weekly.

I'll defer to @d0cd about the usage and implementation of types_structurally_equivalent and whether he seed potential impending issues related to dynamic dispatch.

@mikebenfield
Copy link
Contributor Author

Here's some updates.

You also asked me to walk you through the changes in cast.rs, which I didn't do yesterday. It's actually pretty straightforward so I'll just list what I've done here. The point is just to pull out the code that previously just applied to Plaintext::Struct into separate functions so it can be called when finding either Plaintext::Struct or Plaintext::ExternalStruct:

  • Previously the code to actually carry out casting to structs was carried out in two places: the cast_to_struct function handled console values, while the circuit values were handled inline in the execute function. I factored out that common code to a cast_to_struct_common macro, used that to define separate execute_cast_to_struct and evaluate_cast_to_struct functions, and then called those functions when evaluating or executing where the destination type is Struct or ExternalStruct.

  • Previously the code did a bunch of checks inline in output_types when the dest type what a struct. I pulled those checks into a closure called struct_checks, and now call that when matching either a Struct or an ExternalStruct.

@vicsn
Copy link
Collaborator

vicsn commented Jul 30, 2025

Can you use this to confirm that the R1CS for all known deployments remains unchanged? https://github.com/ProvableHQ/aleo-program-regressions/tree/master/r1cs-hasher

EDIT: It's been a while since we touched it, so may need some minor refactoring. Specifically, I see it is unclear which deployments have been used for the previous hash, I assume the first 211 of either canary/testnet/mainnet because the hash file name ends with an inexplicable _211. I'd advise computing a hash from scratch with all programs, using staging and struct-namespace snarkVM versions.

@mikebenfield
Copy link
Contributor Author

Can you make a PR to add clarification to the docs:

Sure.

@mikebenfield
Copy link
Contributor Author

mikebenfield commented Jul 30, 2025

Can you use this to confirm that the R1CS for all known deployments remains unchanged?

I can't get this to work on recent snarkvm commits. Using snarkvm staging branch (after making a couple changes to main.rs to account for snarkvm API changes):

% RUST_BACKTRACE=1 ~/.cargo/bin/r1cs-hasher programs/mainnet external-struct-mainnet
Processed 282 deployments

Initializing 'TestRng' with seed '0'


thread '<unnamed>' panicked at /Users/michaelbenfield/Code/snarkVM/circuit/environment/src/circuit.rs:293:9:
Surpassed the variable limit (41859)

UPDATE written by Victor: the tests passed now.

@mikebenfield
Copy link
Contributor Author

Regarding the docs about all the commands/instructions that compare equality of structs. Here's the actual behavior (both before and after this PR; it's unchanged):

  1. If you're trying to compare two struct values and their types have different names, the program is rejected and snarkvm doesn't even try to run it;
  2. If you're trying to compare two struct values and their types have the same name but different structure (like different member names or types), the program begins running but there will be a runtime error (not a failed constraint);
  3. If you're trying to compare two struct values and their types have the same name and structure, then the comparison will work as expected.

I think this is not really behavior we want users to think about, but instead just want to users to only try to compare two struct values of the same type. For that reason I'm inclined to leave the documentation of those instructions as is, but let me know if you feel otherwise.

Change tag for ArrayType element type.

Introduce and use finalize_types_structurally_equivalent.

Use types_structurally_equivalent in synthesizer/process/src/stack/finalize_types/matches.rs

`ProgramID` with backticks in comments.
@mikebenfield mikebenfield requested a review from d0cd August 29, 2025 16:01
@mikebenfield
Copy link
Contributor Author

@d0cd I've checked your questions again; I added a couple comments but I think the code as of the last push had addressed all the issues you raised. Let me know what you think.

);
}

let stack = self.process.read().get_stack(deployment.program_id())?;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the stack guaranteed to exist at this point?

Copy link
Collaborator

@vicsn vicsn Sep 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ooff good catch. No it's not. And we don't even need it, we just read the program from it. We should add some integration tests in a new test_v11.rs file. You can copy behaviour from test_v10.rs and test_v9.rs: e.g. simply create a transaction with external struct before the V11 height, should fail to verify, after the V11 height, should succeed to verify.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good grief I'm a mess; sorry. I actually already fixed this locally before I even pushed this but somehow pushed the wrong commit. I've now pushed the fix, along with the changes to comments. I'll add the test_v11.rs shortly.

// If the `CONSENSUS_VERSION` is greater than or equal to `V9`, then verify that:
// - the program checksum is present in the deployment
// - the program owner is present in the deployment
// If the `CONSENSUS_VERSION` is less than `V10`, then verify that:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// If the `CONSENSUS_VERSION` is less than `V10`, then verify that:
// If the `CONSENSUS_VERSION` is less than `V11`, then verify that:

// - the program checksum is present in the deployment
// - the program owner is present in the deployment
// If the `CONSENSUS_VERSION` is less than `V10`, then verify that:
// - the program does not use the external struct syntax `some_program.aleo/StructT`
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// - the program does not use the external struct syntax `some_program.aleo/StructT`
// - the program does not use the external struct syntax `some_program.aleo/StructT`
// - the program's mappings do not use non-existent structs.

}
// Ensure the operands are of the same type.
if input_types[0] != input_types[1] {
if !register_types_structurally_equivalent(stack, &input_types[0], stack, &input_types[1])? {
Copy link
Collaborator

@vicsn vicsn Sep 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Forking risk, transaction validity changed

Also change type equivalence to require structs have the same name.
- external_struct_fail_name test

- `plaintext_exists` moved to `Process` as `mapping_types_exist`.
@vicsn
Copy link
Collaborator

vicsn commented Sep 26, 2025

@mikebenfield one of the synthesizer tests halted with a mysterious VM safely halted at /home/circleci/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/itertools-0.14.0/src/zip_eq_impl.rs:50:17: itertools: .zip_eq() reached end of one iterator before the other. Would be great if you can try to reproduce.

Note that a small improvement to test_vm_execute_and_finalize tests along the way may both speed up your debugging and be useful for the future:

  • would there have been any way for this custom test runner to log which test failed on this particular error, so that for reproducing you'd only have to run a single one?
  • speaking of running a single test, it is currently not possible or easy to see how to choose a single sub-test to run in test_vm_execute_and_finalize

@mikebenfield
Copy link
Contributor Author

@vicsn Alright I fixed that test but unfortunately didn't enhance the test function.

There's now a semver check failing, but I don't think that is related to this PR.

@vicsn
Copy link
Collaborator

vicsn commented Nov 5, 2025

@mohammadfawaz can records contain structs and/or external structs? Do we have tests for this in place? CC @Antonio95 for his work on dynamic dispatch

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants